package org.example.nio;

import org.example.util.ByteBufferUtil;
import org.example.util.PathUtil;
import org.junit.Test;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;

public class BufferExample {

    private PathUtil  pathUtil = new PathUtil();

    /**
     * ByteBuffer
     */
    @Test
    public void ByteBufferTest(){

        try(InputStream inputStream = getClass().getClassLoader().getResourceAsStream("a.txt");
            ReadableByteChannel channel = Channels.newChannel(inputStream)) {
            // 获得缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(10);
            int hasNext = 0;
            StringBuilder builder = new StringBuilder();
            while((hasNext = channel.read(buffer)) > 0) {
                // 切换模式 limit=position, position=0
                buffer.flip();
                // 当buffer中还有数据时，获取其中的数据
                while(buffer.hasRemaining()) {
                    builder.append((char)buffer.get());
                }
                // 切换模式 position=0, limit=capacity
                buffer.clear();
            }
            System.out.println(builder.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void ByteBufferCoreApiTest(){
        ByteBuffer buffer = ByteBuffer.allocate(10);
        // 向buffer中写入1个字节的数据
        buffer.put((byte)97);
        // 使用工具类，查看buffer状态
        ByteBufferUtil.debugAll(buffer);

        // 向buffer中写入4个字节的数据
        buffer.put(new byte[]{98, 99, 100, 101});
        ByteBufferUtil.debugAll(buffer);

        // 获取数据
        buffer.flip();
        ByteBufferUtil.debugAll(buffer);
        System.out.println(buffer.get());
        System.out.println(buffer.get());
        ByteBufferUtil.debugAll(buffer);

        // 使用compact切换模式
        buffer.compact();
        ByteBufferUtil.debugAll(buffer);

        // 再次写入
        buffer.put((byte)102);
        buffer.put((byte)103);
        ByteBufferUtil.debugAll(buffer);
    }

    @Test
    public void ByteBufferWrapTest(){
        // 准备两个字符串
        String str1 = "hello";
        String str2 = "";

        // 通过StandardCharsets的encode方法获得ByteBuffer
        // 此时获得的ByteBuffer为读模式，无需通过flip切换模式
        ByteBuffer buffer1 = ByteBuffer.wrap(str1.getBytes());
        ByteBufferUtil.debugAll(buffer1);

        // 将缓冲区中的数据转化为字符串
        // 通过StandardCharsets解码，获得CharBuffer，再通过toString获得字符串
        str2 = StandardCharsets.UTF_8.decode(buffer1).toString();
        System.out.println(str2);
        ByteBufferUtil.debugAll(buffer1);
    }

    @Test
    public void stickPackage(){
        ByteBuffer buffer = ByteBuffer.allocate(32);
        // 模拟粘包+半包
        buffer.put("Hello,world\nI'm Nyima\nHo".getBytes());
        // 调用split函数处理
        split(buffer);
        buffer.put("w are you?\n".getBytes());
        split(buffer);
    }

    private static void split(ByteBuffer buffer) {
        // 切换为读模式
        buffer.flip();
        for(int i = 0; i < buffer.limit(); i++) {

            // 遍历寻找分隔符
            // get(i)不会移动position
            if (buffer.get(i) == '\n') {
                // 缓冲区长度
                int length = i+1-buffer.position();
                ByteBuffer target = ByteBuffer.allocate(length);
                // 将前面的内容写入target缓冲区
                for(int j = 0; j < length; j++) {
                    // 将buffer中的数据写入target中
                    target.put(buffer.get());
                }
                // 打印查看结果
                ByteBufferUtil.debugAll(target);
            }
        }
        // 切换为写模式，但是缓冲区可能未读完，这里需要使用compact
        buffer.compact();
    }

    /**
     * Scattering Reads是指数据从一个channel读取到多个buffer中
     */
    @Test
    public void scatterTest() throws Exception {
        // 获取文件的相对路径
        String relativePath = "sacttering.txt";
        // 获取文件的绝对路径
        String absolutePath = pathUtil.getAbsolutePath(relativePath);
        // 创建一个 RandomAccessFile 对象，用于读取文件
        RandomAccessFile file = new RandomAccessFile(absolutePath, "rw");
        // 获取文件通道
        FileChannel channel = file.getChannel();
        // 创建两个 ByteBuffer 对象，分别用于存储文件头和文件体
        ByteBuffer header = ByteBuffer.allocate(5);
        ByteBuffer body = ByteBuffer.allocate(6);

        // 创建一个 ByteBuffer 数组，用于存储 header 和 body
        ByteBuffer[] bufferArray = {header, body};
        // 将文件内容读取到 bufferArray 中
        channel.read(bufferArray);
        ByteBufferUtil.debugAll(header);
        ByteBufferUtil.debugAll(body);
        // 关闭文件通道
        channel.close();

        // 关闭 RandomAccessFile 对象
        file.close();
    }

    /**
     * Gathering ：Gathering Writes是指数据从多个buffer写入到同一个channel
     */
    @Test
    public void gatheringTest() throws Exception {
        String stream1 = "Gather data stream first";
        String stream2 = "Gather data stream second";
        ByteBuffer bLen1 = ByteBuffer.allocate(1024);
        ByteBuffer bLen2 = ByteBuffer.allocate(1024);
        // Next two buffer hold the data we want to write
        ByteBuffer bstream1 = ByteBuffer.wrap(stream1.getBytes());
        ByteBuffer bstream2 = ByteBuffer.wrap(stream2.getBytes());
        int len1 = stream1.length();
        int len2 = stream2.length();
        // Writing length(data) to the Buffer
        bLen1.asIntBuffer().put(len1);
        bLen2.asIntBuffer().put(len2);
        System.out.println("Gathering : Len1 = " + len1);
        System.out.println("Gathering : Len2 = " + len2);
        // Write data to the file
        try {
            // 获取文件的相对路径
            String relativePath = "gathering.txt";
            // 获取文件的绝对路径
            String absolutePath = pathUtil.getAbsolutePath(relativePath);
            FileOutputStream out = new FileOutputStream(absolutePath);
            GatheringByteChannel gather = out.getChannel();
            gather.write(new ByteBuffer[] {bLen1, bLen2, bstream1, bstream2});
            out.close();
            gather.close();
        }
        catch (FileNotFoundException exObj) {
            exObj.printStackTrace();
        }catch(IOException ioObj) {
            ioObj.printStackTrace();
        }
    }


}
